/*
 * Copyright (C) 2012 Caldia Apps.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.caldia.android.frameworkG1.messagingcontroller;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import android.os.Process;

/**
 * メッセージコントローラー
 *
 * @param <T>
 *            メッセージングリスナー
 * @author k-matsuda@caldia.org
 *
 */
public abstract class MessagingController<T extends MessagingListener>
		implements
			Runnable {

	/**
	 * シーケンス
	 */
	private static AtomicInteger sSequencing = new AtomicInteger(0);

	/**
	 * スレッド
	 */
	private final Thread mThread;

	/**
	 * キュー
	 */
	private final BlockingQueue<Command<T>> mCommands = new PriorityBlockingQueue<Command<T>>();

	/**
	 * メッセージリスナーセット
	 */
	private final Set<T> mListeners = new CopyOnWriteArraySet<T>();

	/**
	 * コンストラクタ
	 */
	protected MessagingController() {

		// スレッドを作成
		mThread = new Thread(this);

		// スレッド名を設定
		mThread.setName(MessagingController.class.getSimpleName());

		// スレッド開始
		mThread.start();

	}

	@Override
	public void run() {

		boolean isInterruped = false;
		// スレッド優先度 - バックグラウンド
		Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

		while (!isInterruped) {

			try {

				final Command<T> command = mCommands.take();

				if (command != null) {

					// 情報ログ
//					CLog.i(this, 0, "Command - " + command.description + " -");

					command.runnable.run();

					for (final MessagingListener l : getListeners(command.listener)) {
						try {
							// コマンド処理完了イベント
							l.controllerCommandCompleted(command.description,
									!mCommands.isEmpty());
						} catch (final Exception e) {
							e.printStackTrace();
//							CLog.d(this, 0, "" + e.getMessage(), e);
						}
					}

				}

			} catch (final InterruptedException e) {
				e.printStackTrace();
				isInterruped = true;
			} catch (final Exception e) {
//				CLog.e(this, 0, "" + e.getMessage(), e);
				e.printStackTrace();
			}

		}

	}

	/**
	 * メッセージングリスナー取得
	 *
	 * @param l
	 *            今回限り追加したいリスナー
	 * @return リスナーセット
	 */
	public Set<T> getMessagingListeners(final T l) {

		final HashSet<T> set = new HashSet<T>(mListeners);

		if (l != null) {
			set.add(l);
		}

		return set;
	}

	/**
	 * コマンドを追加
	 *
	 * @param runnable
	 *            コマンド実行内容
	 * @param description
	 *            コマンド説明
	 * @param l
	 *            メッセージリスナー
	 */
	public void putCommand(final String description, final Runnable runnable,
			final T l) {

		putCommand(description, runnable, false, l);

	}

	/**
	 * コマンドを追加
	 *
	 * @param runnable
	 *            コマンド実行内容
	 * @param description
	 *            コマンド説明
	 * @param l
	 *            メッセージリスナー
	 */
	public void putCommandBackground(final String description,
			final Runnable runnable, final T l) {

		putCommand(description, runnable, false, l);

	}

	/**
	 * コマンドを追加
	 *
	 * @param runnable
	 *            コマンド実行内容
	 * @param description
	 *            コマンド説明
	 * @param isForeground
	 *            フォアグランド（優先度）
	 * @param l
	 *            メッセージリスナー
	 */
	public void putCommand(final String description, final Runnable runnable,
			final boolean isForeground, final T l) {

		put(runnable, description, isForeground, l);

	}

	/**
	 * コマンドを追加
	 *
	 * @param runnable
	 *            コマンド実行内容
	 * @param description
	 *            コマンド説明
	 * @param isForeground
	 *            フォアグランド（優先度）
	 * @param l
	 *            メッセージリスナー
	 */
	private void put(final Runnable runnable, final String description,
			final boolean isForeground, final T l) {

		// キューをキャッシュ
		final BlockingQueue<Command<T>> queue = mCommands;

		// リトライ回数
		final int i = 10;
		int retries = i;
		Exception e = null;

		while (retries-- > 0) {
			try {

				// キューにコマンドを追加する
				queue.put(new Command<T>(runnable, l, description, isForeground));

				// コマンド追加が成功したら処理を終了
				return;

			} catch (final InterruptedException ie) {
				try {

					// リトライスリープ時間
					final int j = 200;
					Thread.sleep(j);

				} catch (final InterruptedException ne) {
//					CLog.dt(this, retries, "" + ne.getLocalizedMessage());
					ne.printStackTrace();
				}
				e = ie;
			}
		}

		// リトライ回数オーバー
		throw new Error(e);

	}

	/**
	 * メッセージリスナーセット取得
	 *
	 * @return メッセーリスナーセット
	 */
	public Set<T> getListeners() {
		return mListeners;
	}

	/**
	 * メッセージリスナーセット取得
	 *
	 * @param listener
	 *            セットに追加するリスナー
	 * @return メッセージリスナーセット
	 */
	public Set<T> getListeners(final T listener) {

		final Set<T> listeners = new HashSet<T>(mListeners);
		if (listener == null) {
			return listeners;
		}

		// メッセージ追加
		listeners.add(listener);
		return listeners;

	}

	/**
	 * メッセージリスナー追加
	 *
	 * @param listener
	 *            メッセージリスナー
	 */
	public void addListener(final T listener) {

		mListeners.add(listener);
	}

	/**
	 * メッセージリスナー削除
	 *
	 * @param listener
	 *            メッセージリスナー
	 */
	public void removeListener(final T listener) {

		mListeners.remove(listener);
	}

	/**
	 * コマンド
	 *
	 * @author matsuda_kouichi
	 *
	 */
	static final class Command<T extends MessagingListener>
			implements
				Comparable<Command<T>> {

		/**
		 * コマンド内容
		 */
		private final Runnable runnable;

		/**
		 * メッセージリスナー
		 */
		private final T listener;

		/**
		 * コマンド説明分
		 */
		private final String description;

		/**
		 * フォアグランド（優先度）
		 */
		private final boolean isForeground;

		/**
		 * シーケンス番号
		 */
		private final int sequence = sSequencing.getAndIncrement();

		/**
		 * コンストラクタ
		 *
		 * @param runnable
		 *            実行する内容
		 * @param l
		 *            メッセージリスナー
		 * @param description
		 *            コマンド説明
		 * @param isForeground
		 *            フォアグランド（優先度）
		 */
		private Command(final Runnable runnable, final T l,
				final String description, final boolean isForeground) {

			// パラメータチェック
			if (runnable == null) {
				throw new IllegalArgumentException();
			}

			// パラメータチェック
			if (l == null) {
				throw new IllegalArgumentException();
			}

			this.runnable = runnable;
			this.listener = l;
			this.description = description;
			this.isForeground = isForeground;
		}

		@Override
		public int compareTo(final Command<T> other) {

			if (other.isForeground && !isForeground) {
				return 1;
			} else if (!other.isForeground && isForeground) {
				return -1;
			} else {
				// シーケンス番号比較
				return (sequence - other.sequence);
			}
		}
	}

}
